package com.metis.document.parse.dialog.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.metis.document.parse.dialog.api.ParseControllerApi;
import com.metis.document.parse.dialog.api.PostProcessControllerApi;
import com.metis.document.parse.dialog.constant.FileConstantFilter;
import com.metis.document.parse.dialog.constant.FileConstantFilter;
import com.metis.document.parse.dialog.entity.ParseToJsonResult;
import com.metis.document.parse.dialog.enums.FileTypeEnum;
import com.metis.document.parse.dialog.models.dto.*;
import com.metis.document.parse.dialog.models.vo.JsonContentVO;
import com.metis.document.parse.dialog.pipeline.*;
import com.metis.document.parse.dialog.entity.ParseParameter;
import com.metis.document.parse.dialog.service.ParseService;
import com.metis.document.parse.dialog.utils.AutoCleanFile;
import com.metis.document.parse.dialog.utils.ByteArrayMultipartFile;
import com.metis.document.parse.dialog.utils.ScanPDFDetector;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
public class ParseServiceImpl implements ParseService {
    private static final String MD_SEPARATE = "\n\n";

    private final static String DEFAULT_RESPONSE_ZIP_FILE_NAME = "output";

    @Autowired
    private ParseControllerApi parseControllerApi;
    @Autowired
    private PostProcessControllerApi postProcessControllerApi;
    @Autowired
    private List<TextFilter> textFilters = new ArrayList<>();
    @Autowired
    private List<TextPostProcess> textPostProcesses = new ArrayList<>();
    @Autowired
    private List<ElementFilter> elementFilters = new ArrayList<>();
    @Autowired
    private List<TitleTextPostProcess> titleTextPostProcesses = new ArrayList<>();
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private ProgramFileFilter programFileFilter;

    @Value("${post-process.enabled}")
    private boolean enabled;
    @Autowired
    private List<TitleTextFilter> titleTextFilters = new ArrayList<>();

    @Override
    public void parsePDF(@NotNull ParseParameter parameter) {
        // 添加前置验证 区分是否为pdf 扫描件 如果是扫描件 不调解析接口 直接返回 提示信息
        String fileType = parameter.getFileType();
        if(StringUtils.isNotEmpty(fileType) && fileType.equals("pdf")){
             Boolean isPdf = ScanPDFDetector.isPdf(parameter.getFile());
            if(!isPdf){
                List<Pair<String, AutoCleanFile>> outputFiles = new ArrayList<>();
                Set<String> programTag = new LinkedHashSet<>();
                programTag.add("检测认为该文件为pdf扫描件：" + parameter.getFileName());
//                collectProgramTagAndSaveToFile(outputFiles, programTag);
//                generateMdFile(outputFiles,parameter.getFileName());
//                generateJsonFile(outputFiles,parameter.getFileName());
//                writeZipToOutStream(outputFiles, parameter.getOs());
                return;
            }
        }

        // 验证文件类型 和 文件名称
        if(StringUtils.isEmpty(parameter.getFileType())|| !FileConstantFilter.fleTypeExits(parameter.getFileType().toLowerCase(Locale.ROOT))){
            log.error("the type of file is illegal: " + parameter.getFileType());
            throw new IllegalStateException("the type of file is illegal: " + parameter.getFileType());
        }

        if(StringUtils.isEmpty(parameter.getFileName()) || FileConstantFilter.fileNameExists(parameter.getFileName())){
            log.error("the name of file is illegal: " + parameter.getFileName());
            throw new IllegalStateException("the name of file is illegal: " + parameter.getFileName());
        }

        ResponseEntity<ParseResultDTO> responseEntity = parseControllerApi.parsePDF(parameter.getFile(), true, true, true, true, true, true, Collections.emptyList());
        ParseResultDTO parseResultDTO = responseEntity.getBody();
        parsePdfDTO(parseResultDTO, parameter.getOs());
    }

    /**
     * 兼容 parsePDF
     * @param file file
     * @param os os
     */
    @Override
    public void parsePDF(@NotNull ByteArrayMultipartFile file, @NotNull OutputStream os) {
        ResponseEntity<ParseResultDTO> responseEntity = parseControllerApi.parsePDF(file, true, true, true, true, true, true, Collections.emptyList());
        ParseResultDTO parseResultDTO = responseEntity.getBody();
        parsePdfDTO(parseResultDTO, os);
    }


    /**
     * parse pdf dto 接收引擎解析后dto 。后处理核心方法
     *
     * @param parseResultDTO parseResultDTO
     * @param os os
     */
    @Override
    public void parsePdfDTO(ParseResultDTO parseResultDTO, OutputStream os){
        try {
            List<Pair<String, AutoCleanFile>> outputFiles = getOutputFile(Objects.requireNonNull(parseResultDTO));
            if(outputFiles.size() ==1 & outputFiles.get(0).getKey() == "flag.txt")
                return;
            writeZipToOutStream(outputFiles, os);
        } catch (Exception e) {
            log.error("处理解析文件出错", e);
            throw new RuntimeException(e);
        }
    }




    /**
     * 生成空md
     *
     * @param result
     * @param fileName
     */
    private void generateMdFile(List<Pair<String, AutoCleanFile>> result, String fileName) {

        AutoCleanFile temp  = new AutoCleanFile(FileTypeEnum.MD);
        FileOutputStream tos = null;
        try {
            tos = new FileOutputStream(temp);
            tos.write(" ".getBytes(StandardCharsets.UTF_8));

            result.add(Pair.of(String.format("%s.md", fileName), temp));
        } catch (IOException e) {
            log.error("write markdown error", e);
            throw new IllegalStateException(e);
        }finally {
            if(tos != null){
                try {
                    tos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }


    /**
     * 生成空json
     *
     * @param result
     * @param fileName
     */
    private void generateJsonFile(List<Pair<String, AutoCleanFile>> result, String fileName) {

        AutoCleanFile temp = new AutoCleanFile(FileTypeEnum.JSON);
        FileOutputStream tos = null;
        try {
            tos = new FileOutputStream(temp);
            tos.write(" ".getBytes(StandardCharsets.UTF_8));
            result.add(Pair.of(String.format("%s.json", fileName), temp));
        } catch (IOException e) {
            log.error("write json error", e);
            throw new IllegalStateException(e);
        }finally {
            if(tos != null){
                try {
                    tos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }



    /**
     * 将最终压缩包写入输出流
     *
     * @param outputFiles outputFiles
     * @param os os
     */
    private void writeZipToOutStream(List<Pair<String, AutoCleanFile>> outputFiles, OutputStream os) {
        try(ZipArchiveOutputStream responseOutput = new ZipArchiveOutputStream(new BufferedOutputStream(os))){
            for (Pair<String, AutoCleanFile> pair : outputFiles) {
                ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(pair.getKey());
                zipArchiveEntry.setSize(pair.getValue().length());
                responseOutput.putArchiveEntry(zipArchiveEntry);
                try(FileInputStream in = new FileInputStream(pair.getValue())){
                    IOUtils.copy(in, responseOutput);
                }
            }
            responseOutput.closeArchiveEntry();
            responseOutput.finish();
            responseOutput.flush();
        }catch (Exception e) {
            log.error("解析文件写出出错", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * getOutputFile
     * @param parseResultDTO parseResultDTO
     * @return list
     * @throws Exception exp
     */
    private List<Pair<String, AutoCleanFile>> getOutputFile(@NotNull ParseResultDTO parseResultDTO) throws Exception {

        String baseName = DEFAULT_RESPONSE_ZIP_FILE_NAME;
        List<Pair<String, AutoCleanFile>> result = new ArrayList<>();

        //过滤乱码
        boolean isChaosFile = ChaosFileFilter.filterChaosFile(parseResultDTO);
        if(isChaosFile) {
            log.info("当前文章乱码");
            throw new Exception("当前文章乱码");
        }

        //解析前 检测 问题样本标记
        Set<String> programTag = new LinkedHashSet<>();
        programFileFilter.detect(parseResultDTO,programTag);

        //json
        ParseToJsonResult jsonResult = toJsonIS(parseResultDTO);
        // code == 1 代表 解析后 字符长度 基本为空的情况(小于某个阀值)
        if(jsonResult.getCode() == 1){
            //programTag.add(jsonResult.getMsg());
            //collectProgramTagAndSaveToFile(result, programTag);
            //return result;
            throw new Exception("文件解析成JSON后内容基本为空");
        }
        result.add(Pair.of(String.format("%s.json", baseName), jsonResult.getCleanFile()));

        //markdown
        result.add(Pair.of(String.format("%s.md", baseName), toMarkdownIS(parseResultDTO)));

        //图片 hashcode作为文件名
        result.addAll(toImagesIS(parseResultDTO));

        // 收集 问题标记 写入文件
        programTag.add(jsonResult.getMsg());
        collectProgramTagAndSaveToFile(result, programTag);

        return result;
    }

    private List<Pair<String, AutoCleanFile>> toImagesIS(@NotNull ParseResultDTO parseResultDTO) {
        List<Pair<String, AutoCleanFile>> result = new ArrayList<>();
        for (ImageElementDTO image : parseResultDTO.getImages()) {
            String imageCache = image.getImage();
            AutoCleanFile temp = new AutoCleanFile(FileTypeEnum.PNG);
            FileOutputStream tos = null;
            try {
                //base64图片转文件
                tos = new FileOutputStream(temp);
                String imageBase64 = imageCache.substring(imageCache.indexOf(",") + 1);
                byte[] bytes = Base64.getDecoder().decode(imageBase64);
                tos.write(bytes);
                result.add(Pair.of(String.format("image/%s", getImageFilename(imageCache)), temp));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }finally {
                if(tos != null){
                    try {
                        tos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return result;
    }

    private AutoCleanFile toMarkdownIS(@NotNull ParseResultDTO parseResultDTO) {
        String markdown = toMarkdown(parseResultDTO);
        // 如果markdown是空 则直接抛出异常
        if(StringUtils.isEmpty(markdown)){
            log.error("write markdown error, cause the length of markdown is zero!");
            throw new IllegalStateException("write markdown error, cause the length of markdown is zero!");
        }
        AutoCleanFile temp  = new AutoCleanFile(FileTypeEnum.MD);
        FileOutputStream tos = null;
        try {
            tos = new FileOutputStream(temp);
            tos.write(markdown.getBytes(StandardCharsets.UTF_8));
            return temp;
        } catch (IOException e) {
            log.error("write markdown error", e);
            throw new IllegalStateException(e);
        }finally {
            if(tos != null){
                try {
                    tos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private ParseToJsonResult toJsonIS(@NotNull ParseResultDTO parseResultDTO) {
        String json = toJson(parseResultDTO);

        /*
         * 针对json结构调用 python 工具接口 再次清洗 202403221457
         * {
         * 	"data_type": "string",
         * 	"id": 0,
         * 	"title": "string",
         * 	"content": [
         *                {
         * 			"origin_title": "string",
         * 			"section_content": "string",
         * 			"section_title": "string",
         * 			"section_id": 0,
         * 			"title_level": "string"
         *        }
         * 	]
         * }
         */
        try {
            Map<String, Object> jsonData = new HashMap<>();
            jsonData.put("data_type","");
            jsonData.put("id",0);
            jsonData.put("title","");
            jsonData.put("content", JSONObject.parseArray(json));

            if(enabled){
                ResponseEntity<String> re = postProcessControllerApi.processDocument(jsonData);
                if(re.getStatusCode() == HttpStatus.OK){
                    json = re.getBody();
                }
            }
        }catch (Exception e){
            log.error(e.getMessage());
        }


        // 后处理结束后 验证
        ParseToJsonResult rs = checkAfterPostProcessPython(json);
        if(null != rs){
            return rs;
        }

        // 正常结束解析流程
        ParseToJsonResult result = new ParseToJsonResult();
        AutoCleanFile temp = new AutoCleanFile(FileTypeEnum.JSON);
        FileOutputStream tos = null;
        try {
            tos = new FileOutputStream(temp);
            tos.write(json.getBytes(StandardCharsets.UTF_8));

            result.setCode(0);
            result.setCleanFile(temp);
            return result;
        } catch (IOException e) {
            log.error("write json error", e);
            throw new IllegalStateException(e);
        }finally {
            if(tos != null){
                try {
                    tos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private String toJson(@NotNull ParseResultDTO parseResultDTO) {
        List<JsonContentVO> jsonContentVOS = new ArrayList<>();
        //将所有要素进行排序
        Set<AbstractElementDTO> sbSet = new TreeSet<>();
        sbSet.addAll(parseResultDTO.getParagraphs());
        sbSet.addAll(parseResultDTO.getImages());
        sbSet.addAll(parseResultDTO.getTables());
        sbSet.addAll(parseResultDTO.getEquation());
        Map<ParagraphTextElementDTO, String> titleId2Title = new HashMap<>();
        //生成json
        Optional<ParagraphTextElementDTO> title = Optional.empty();
        Iterator<AbstractElementDTO> iterator = sbSet.iterator();
        boolean lastIsParagraphContent = false;
        while (iterator.hasNext()) {
            AbstractElementDTO element = iterator.next();
            try {
                for (ElementFilter elementFilter : elementFilters) {
                    if (elementFilter.support(element) && elementFilter.needIgnore(element)){
                        continue;
                    }
                }
                switch (element.getType()) {
                    case PARAGRAPH:
                        ParagraphTextElementDTO paragraph = (ParagraphTextElementDTO) element;
                        if (Objects.nonNull(paragraph.getLevel())) {
                            String titleContent = paragraph.getText();

//                            for (TitleTextPostProcess titleTextPostProcess : titleTextPostProcesses) {
//                                titleContent = titleTextPostProcess.process(titleContent);
//                            }
                            if(titleTextProcess(titleContent)) break;
                            titleId2Title.put(paragraph, titleContent);
                            //如果是标题，则单独 保存
                            title = Optional.of(paragraph);
                            JsonContentVO current = JsonContentVO.builder().sectionId(jsonContentVOS.size() + 1).build();
                            current.setTitleLevel(paragraph.getLevel());
                            current.setOriginTitle(titleContent);
                            //appendCoordPaginate(element, current);
                            //重置上下文
                            jsonContentVOS.add(current);
                        } else {
                            //否则单独保存
                            if (jsonContentVOS.isEmpty()) {
                                jsonContentVOS.add(JsonContentVO.builder().sectionId(jsonContentVOS.size() + 1).build());
                            }
                            JsonContentVO current = jsonContentVOS.get(jsonContentVOS.size() - 1);
                            current.setSectionContent(current.getSectionContent() + paragraph.getLineTexts()
                                    .stream()
                                    .filter(line -> textFilters.stream().noneMatch(textFilter -> textFilter.needToFilter(line)))
                                    .collect(Collectors.joining())
                            );
                            if (title.isPresent()) {
                                current.setTitleLevel(title.get().getLevel());
                                current.setOriginTitle(titleId2Title.get(title.get()));
                            }
                            // appendCoordPaginate(element, current);
                            //重置上下文
                            lastIsParagraphContent = true;
                        }

                        break;
                    case TABLE:
                        //使用htmltable
                        JsonContentVO current = JsonContentVO.builder().sectionId(jsonContentVOS.size() + 1).build();
                        String tableHtml = tableToMarkdown((TableElementDTO) element);
                        current.setSectionContent(current.getSectionContent() + tableHtml);

                        // appendCoordPaginate(element, current);
                        jsonContentVOS.add(current);
                        //重置上下文
                        lastIsParagraphContent = false;
                        title = Optional.empty();
                        break;
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }


//        /**
//         * 判断section对象小于3且 title_level全部为null ，根据section_content字符长度 拆分成多个section对象
//         */
//        List<JsonContentVO> returnList = new ArrayList<>();
//        if(jsonContentVOS.size() <= 3 && isAllTitleLevelEmpty(jsonContentVOS)){
//            log.info("title_level为null，全部识别成正文");
//            for (int i = 0; i < jsonContentVOS.size(); i++) {
//                String sectionContent = jsonContentVOS.get(i).getSectionContent();
//                sectionContent = sectionContent.trim();
//                if(sectionContent.length() > 1000) {
//                    //拆分
//                    List<String> sections = splitAndRecombineText(sectionContent);
//                    for (String content : sections) {
//                        JsonContentVO jsonContentVO = new JsonContentVO();
//                        jsonContentVO.setOriginTitle(jsonContentVOS.get(i).getOriginTitle());
//                        jsonContentVO.setSectionContent(content);
//                        if (returnList.isEmpty()) {
//                            jsonContentVO.setSectionId(1);
//                        } else {
//                            jsonContentVO.setSectionId(returnList.size() + 1);
//                        }
//                        jsonContentVO.setRate("1");
//                        returnList.add(jsonContentVO);
//                    }
//                }else {
//                    log.info("字符串长度没超过1200，不需要拆分");
//                }
//            }
//        }else {
//            returnList.addAll(jsonContentVOS);
//        }



        /*
         * 转换为VO
         * 段落标题 乱码 json结果未完全做去重 20240319
         */
        List<JsonContentVO> result = new ArrayList<>();
        //默认1 （1：可用，后处理判断不可用后改为3）
        String rate = "1";
        for (JsonContentVO jsonContentVO : jsonContentVOS) {

            String sectionTitle = titleTextPostProcess(jsonContentVO.getSectionTitle());
            String originTitle = titleTextPostProcess(jsonContentVO.getOriginTitle());
            String sectionContent = textPostProcess(jsonContentVO.getSectionContent());

            JsonContentVO vo = JsonContentVO.builder()
                    .sectionId(jsonContentVO.getSectionId())
                    .titleLevel(jsonContentVO.getTitleLevel())
                    .sectionTitle(sectionTitle)
                    .originTitle(originTitle)
                    .sectionContent(sectionContent)
                    //.startX(jsonContentVO.getStartX())
                    //.startY(jsonContentVO.getStartY())
                    //.pageNum(jsonContentVO.getPageNum())
                    .rate(rate)
                    .build();
            result.add(vo);
        }
        try {
            return objectMapper.writeValueAsString(result);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    public static List<String> splitAndRecombineText(String text) {
        List<String> sections = new ArrayList<>();
        int maxLength = 500;
        // 根据句号分割文本
        String[] sentences = text.split("。");
        StringBuilder currentSection = new StringBuilder();
        for (String sentence : sentences) {
            if (currentSection.length() + sentence.length() < maxLength) {
                currentSection.append(sentence).append("。");
            } else {
                sections.add(currentSection.toString());
                currentSection = new StringBuilder();
                currentSection.append(sentence).append("。");
            }
        }
        if (currentSection.length() > 0) {
            sections.add(currentSection.toString());
        }
        return sections;
    }


    public static boolean isAllTitleLevelEmpty(List<JsonContentVO> contentList) {
        for (JsonContentVO content : contentList) {
            String titleLevel = String.valueOf(content.getTitleLevel());
            if (StringUtils.isNotEmpty(titleLevel)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 转换为markdown
     */
    @SneakyThrows
    public String toMarkdown(@NotNull ParseResultDTO parseResultDTO) {
        List<String> result = new ArrayList<>();
        //将所有要素进行排序
        Set<AbstractElementDTO> sbSet = new TreeSet<>();
        sbSet.addAll(parseResultDTO.getParagraphs());
        sbSet.addAll(parseResultDTO.getImages());
        sbSet.addAll(parseResultDTO.getTables());
        sbSet.addAll(parseResultDTO.getEquation());
        //生成markdown
        for (AbstractElementDTO element : sbSet) {
            try {
                for (ElementFilter elementFilter : elementFilters) {
                    if (elementFilter.support(element) && elementFilter.needIgnore(element)){
                        continue;
                    }
                }
                switch (element.getType()) {
                    case PARAGRAPH:
                        ParagraphTextElementDTO paragraph = (ParagraphTextElementDTO) element;
                        if (Objects.nonNull(paragraph.getLevel())) {
                            String titleContent = paragraph.getText();
                            for (TitleTextPostProcess titleTextPostProcess : titleTextPostProcesses) {
                                titleContent = titleTextPostProcess.process(titleContent);
                            }
                            int level = paragraph.getLevel();
                            if (level < 7) {
                                String titlePrefix = StringUtils.repeat("#", level);
                                titlePrefix = titlePrefix + " " +titleContent.replace("\n", "")
                                ;
                                result.add(titlePrefix);
                                break;
                            }
                        }
                        String pContent = paragraphTextElementToMarkdown(paragraph);

                        result.add(textPostProcess(pContent)
                                .replaceAll("^( {4,}|\t)", "   "));
                        break;
//                    case EQUATION:
//                        EquationElementDTO equation = (EquationElementDTO) element;
//                        result.add(String.format("$$%s$$", equation.getEquation()));
//                        break;
//                    case IMAGE:
//                        result.add(String.format("![](image/%s)", getImageFilename(((ImageElementDTO) element).getImage())));
//                        break;
                    case TABLE:
                        TableElementDTO table = (TableElementDTO) element;
                        result.add(tableToMarkdown(table));
                        break;
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        return String.join(MD_SEPARATE, result);
    }

    /**
     * check after post process python
     *
     * @param json json
     */
    private ParseToJsonResult checkAfterPostProcessPython(String json){
        String rs = programFileFilter.totalJsonSize(json);
        if(StringUtils.isNotEmpty(rs)){
            return new ParseToJsonResult(1,rs);
        }
        return null;
    }

    /**
     * 收集 问题标记 写入文件
     *
     * @param result result
     * @param programTag programTag
     */
    private void collectProgramTagAndSaveToFile(List<Pair<String, AutoCleanFile>> result, Set<String> programTag) {
        programTag = programTag.stream().filter(StringUtils::isNotEmpty).collect(Collectors.toSet());
        if (!programTag.isEmpty()) {
            log.info("当前文章包含问题样本标记，{}", programTag);
            AutoCleanFile temp = new AutoCleanFile(FileTypeEnum.TXT);
            FileOutputStream tos = null;
            try {
                tos = new FileOutputStream(temp);
                for (String tag : programTag) {
                    if(StringUtils.isNotEmpty(tag) && StringUtils.isNotEmpty(tag.trim())){
                        tos.write(tag.getBytes(StandardCharsets.UTF_8));
                        tos.write("\n".getBytes(StandardCharsets.UTF_8));
                    }
                }
                result.add(Pair.of("flag.txt", temp));
            }catch (IOException e) {
                throw new RuntimeException(e);
            }finally {
                if(tos != null){
                    try {
                        tos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } else {
            log.info("当前文章不包含问题样本标记");
        }
    }

    /**
     * append coord paginate
     * 补充坐标信息和页码信息 先注释不启用
     *
     * @param element element
     * @param current current
     */
    private void appendCoordPaginate(AbstractElementDTO element, JsonContentVO current){
        current.setStartX(element.getX());
        current.setStartY(element.getY());
        current.setPageNum(element.getPage());
    }

    private String getImageFilename(@NotNull String base64) {
        int hashcode = Objects.hashCode(base64);
        return String.format("%s%s", hashcode, FileTypeEnum.PNG.getSuffix());
    }

    private String paragraphTextElementToMarkdown(@NotNull ParagraphTextElementDTO paragraphTextElementDTO) {
        return paragraphTextElementDTO.getLineTexts()
                .stream()
                .filter(line -> textFilters.stream().noneMatch(textFilter -> textFilter.needToFilter(line)))
                .collect(Collectors.joining());
    }

    @SneakyThrows
    public String tableToMarkdown(@NotNull TableElementDTO table) {
        try {
            CellElementDTO[][] tableData = new CellElementDTO[table.getRowCount()][table.getColCount()];
            List<CellElementDTO> cells = table.getCells();
            for (CellElementDTO cell : cells) {
                tableData[cell.getRow()][cell.getCol()] = cell;
            }
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < tableData.length; i++) {
                if (i == 1) {
                    sb.append("|");
                    sb.append("---|".repeat(tableData[i].length));
                    sb.append("\n");
                }
                StringJoiner rowSb = new StringJoiner("|", "|", "|");
                for (CellElementDTO s : tableData[i]) {
                    if (s == null) {
                        rowSb.add("");
                    } else {
                        rowSb.add(textPostProcess(s.getTextContent()).replace("\n", ""));
                    }
                }
                sb.append(rowSb).append("\n");
            }
            if (tableData.length == 1) {
                sb.append("|");
                sb.append("---|".repeat(tableData[0].length));
                sb.append("\n");
            }
            return sb.toString();
        } catch (Exception e) {
            log.error("表格转md失败, page{}", table.getPage(), e);
            return "";
        }
    }

    /**
     * 将最终压缩包写入输出流
     *
     * @param outputFiles outputFiles
     * @param os os
     */
//    private void writeZipToOutStream(List<Pair<String, AutoCleanFile>> outputFiles, OutputStream os) {
//        try(ZipArchiveOutputStream responseOutput = new ZipArchiveOutputStream(new BufferedOutputStream(os))){
//            for (Pair<String, AutoCleanFile> pair : outputFiles) {
//                ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(pair.getKey());
//                zipArchiveEntry.setSize(pair.getValue().length());
//                responseOutput.putArchiveEntry(zipArchiveEntry);
//                try(FileInputStream in = new FileInputStream(pair.getValue())){
//                    IOUtils.copy(in, responseOutput);
//                }
//            }
//            responseOutput.closeArchiveEntry();
//            responseOutput.finish();
//            responseOutput.flush();
//        }catch (Exception e) {
//            log.error("解析文件写出出错", e);
//            throw new RuntimeException(e);
//        }
//    }

    /**
     * title text post process
     *
     * @param title title
     * @return string
     */
    private String titleTextPostProcess(String title){
        for (TitleTextPostProcess titleTextPostProcess : titleTextPostProcesses) {
            title = titleTextPostProcess.process(title);
        }
        return title;
    }

    /**
     * 文本后处理
     */
    private String textPostProcess(@NotNull String origin) {

        for (TextPostProcess textPostProcess : textPostProcesses) {
            origin = textPostProcess.process(origin);
        }
        return origin;
    }

    /**
     * 标题过滤器
     */
    private boolean titleTextProcess(String title){
        for (TitleTextFilter titleTextFilter : titleTextFilters) {
            return titleTextFilter.needToFilter(title);
        }
        return false;
    }
}
